import {Channel, Message, Replies} from "amqplib"
import * as winston from 'winston'
import {PlayerRmqProxy, RobotPlayerRmqProxy} from "../player/PlayerRmqProxy"
import {logger} from "../utils/logger";
import {GameTypes} from "./gameTypes"
import {IRoom} from "./IRoom"
import {RoomRegister} from "./RoomRegister"
import Timer = NodeJS.Timer


interface In_Base {
  from: string,
  ip: any,
  payload: any
}


interface In_JoinRoomMessage extends In_Base {
  name: 'joinRoom'
}

interface In_RoomReadyMessage extends In_Base {
  name: 'room/ready'
}

interface In_RoomNextGameMessage extends In_Base {
  name: 'room/next-game'
}

interface IN_RoomLeaveMessage extends In_Base {
  name: 'room/leave'
}

interface IN_RoomReconnectMessage extends In_Base {
  name: 'room/reconnect'
}

interface IN_PipeMessage extends In_Base {
  name: string,
  payload: any
}


type In_Message = In_JoinRoomMessage
  | In_RoomNextGameMessage
  | In_RoomNextGameMessage
  | In_RoomReadyMessage
  | IN_RoomLeaveMessage
  | IN_RoomReconnectMessage
  | IN_PipeMessage

function toMessageBody(buffer: Buffer): In_Message {
  return JSON.parse(buffer.toString())
}

interface UserCenter {
  getPlayerModel(playerId: string): Promise<any>
}


type RmqRoomRep = {
  redisClient: any, gameChannel: Channel, gameQueue: Replies.AssertQueue, cluster: string,
  userCenter: UserCenter
}

export type recoverFunc = (any, RmqRoomRep) => Promise<IRoom>

export default class RoomProxy {

  room: IRoom
  private channel: Channel
  private gameQueue: Replies.AssertQueue
  private spinner: Timer
  private cluster: string
  private roomRegister: RoomRegister

  static async recover(json: any, req: RmqRoomRep, gameType: GameTypes = 'paodekuai', recover: recoverFunc): Promise<RoomProxy> {

    const room = await recover(json, {
      channel: req.gameChannel,
      userCenter: req.userCenter
    })

    room.players.forEach(p => {
      p && p.on('disconnect', room.disconnectCallback)
    })

    const table = room.gameState
    room.players.forEach(p => {
      if (table) {
        p && p.sendMessage('room/refresh', table.restoreMessageForPlayer(p))
      }
      p && room.broadcastRejoin(p)
    })

    return new RoomProxy(room, req, gameType)
  }

  constructor(room, rabbit: RmqRoomRep, gameType: GameTypes = 'paodekuai') {
    this.room = room
    const gameName = gameType;
    this.channel = rabbit.gameChannel
    this.gameQueue = rabbit.gameQueue
    this.cluster = rabbit.cluster
    this.roomRegister = new RoomRegister(rabbit.redisClient)

    this.channel.consume(this.gameQueue.queue, async (message: Message) => {

      try {

        const messageBody = toMessageBody(message.content)

        logger.debug(`============== roomId: ${room._id} - ${messageBody.name} ==============`)
        if (messageBody.name === 'joinRoom') {

          const playerModel = await rabbit.userCenter.getPlayerModel(messageBody.from)

          if (!playerModel) {
            logger.error('the player not exists', messageBody)
          }

          let newPlayer
          if (messageBody.from.startsWith('robot')) {

            newPlayer = new RobotPlayerRmqProxy(playerModel, this.channel, gameName)
          } else {
            newPlayer = new PlayerRmqProxy({
              ...playerModel,
              _id: messageBody.from,
              ip: messageBody.ip
            }, this.channel, gameName)
          }

          if (!playerModel) {
            newPlayer.sendMessage('room/join-fail', {reason: '未知的用户信息'})
            return
          }

          if (room.game.rule.clubPersonalRoom === false) {

          } else if (room.game.rule.share) {
            const fee = room.constructor.roomFee(room.game.rule)
            if (playerModel.gem < fee) {
              newPlayer.sendMessage('room/join-fail', {reason: '房卡不足 无法加入房间。'})
              return
            }
          }

          /*
          const alreadyInRoom = await this.roomRegister.roomNumber(messageBody.from, gameName)

          if (alreadyInRoom && alreadyInRoom !== room._id) {
            newPlayer.sendMessage('room/join-fail', {reason: `您还有一个房间未打完 ${alreadyInRoom}`})
            return
          }
          */

          if (room.canJoin(newPlayer)) {
            newPlayer.sendMessage('room/join-success', {_id: room._id, rule: room.rule, clubShortId: room.clubShortId})
            room.join(newPlayer)
            await this.roomRegister.putPlayerInGameRoom(messageBody.from, gameName, room._id)
          } else {
            newPlayer.sendMessage('room/join-fail', {reason: '房间已满!'})
            return
          }
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        const thePlayer: PlayerRmqProxy = room.getPlayerById(messageBody.from)
        if (messageBody.name === 'forceDissolve') {
          room.forceDissolve();
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/ready') {
          room.ready(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/creatorStartGame') {
          room.creatorStartGame(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/next-game') {
          room.nextGame(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/leave') {
          if (room.leave(thePlayer)) {

            await this.roomRegister.removePlayerFromGameRoom(messageBody.from, gameName)

            thePlayer.sendMessage('room/leave-success', {_id: room._id})
            await this.tryBestStore(rabbit.redisClient, room)
            return
          }
          thePlayer.sendMessage('room/leave-fail', {_id: room._id})
          return
        }

        if (messageBody.name === 'room/reconnect') {
          const playerModel = await rabbit.userCenter.getPlayerModel(messageBody.from);

          if (!playerModel) {
            logger.error('the player not exists', messageBody)
            return
          }

          const newPlayer = new PlayerRmqProxy({
            ...playerModel,
            _id: messageBody.from,
            ip: messageBody.ip
          }, this.channel, gameName)
          newPlayer.sendMessage('room/reconnectReply', {errorCode: 0, _id: room._id, rule: room.rule})
          room.reconnect(newPlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/dissolveReq') {
          room.onRequestDissolve(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }
        if (messageBody.name === 'room/addShuffle') {
          room.addShuffle(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/AgreeDissolveReq') {
          room.onAgreeDissolve(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/DisagreeDissolveReq') {
          room.onDisagreeDissolve(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/dissolve') {
          room.dissolve(thePlayer)
          await this.tryBestStore(rabbit.redisClient, room)
          return
        }

        if (messageBody.name === 'room/buildInChat') {
          messageBody.payload.index = this.room.indexOf(thePlayer) || 0
          this.room.broadcast(messageBody.name, messageBody.payload)
          return
        }

        if (messageBody.name === 'room/sound-chat') {
          messageBody.payload.index = this.room.indexOf(thePlayer) || 0
          this.room.broadcast(messageBody.name, messageBody.payload)
          return
        }
        thePlayer && thePlayer.emit(messageBody.name, messageBody.payload)

        await this.tryBestStore(rabbit.redisClient, room)
      } catch (e) {
        logger.error({
          error: 'handel message error', message: message.content.toString(),
          e: e.message, stack: e.stack
        })
      }
    })
      .then(({consumerTag}) => {


        rabbit.redisClient.setexAsync(`room:${room._id}`, 10, new Date().toTimeString())
        this.spinner = setInterval(() => {
          rabbit.redisClient.setexAsync(`room:${room._id}`, 3, new Date().toTimeString())
        }, 2000)

        this.room.on('leave', async ({_id}) => {
          try {
            await this.roomRegister.removePlayerFromGameRoom(_id, gameType)
          } catch (e) {
            logger.error('removePlayerFromGameRoom', _id, e)
          }
        })

        this.room.once('empty', async () => {
          logger.info('room ', room._id, 'spinner clean')
          logger.info('room', room.playersOrder.filter(p => p).forEach(({_id}) => {
            this.roomRegister.removePlayerFromGameRoom(_id, gameType)
          }))

          clearInterval(this.spinner)
          await this.deleteRoomInfo(rabbit.redisClient, room)

          try {
            await this.channel.cancel(consumerTag)
            await this.channel.close()
          } catch (e) {
            logger.error('channel close failed', e)
          }

          await rabbit.redisClient.rpushAsync('roomIds', room._id)
        })

        this.room.on('qiangLongTou', async () => {
          await this.tryBestStore(rabbit.redisClient, room)
        })
      }, () => {
        return rabbit.redisClient.rpushAsync('roomIds', room._id)
      })
  }

  private async deleteRoomInfo(redis, room) {
    try {
      await redis.sremAsync(`cluster-${this.cluster}`, room._id)
      await redis.delAsync('room:info:' + room._id)
      await redis.delAsync(`room:${room._id}`)

    } catch (e) {
      logger.error(`del room ${room._id} failed with `, e)
    }
  }

  private async tryBestStore(redis, room) {
    try {
      await redis.setAsync('room:info:' + room._id, JSON.stringify(room.toJSON()))
    } catch (e) {
      logger.error(`store room ${room._id} failed with `, e)
    }

  }

  canJoin(player: PlayerRmqProxy) {

    if (this.room.isFull(player)) {
      player.sendMessage('room/join-fail', {reason: '房间人数已满, 请重新输入房间号'})
      return false
    }

    return true
  }


  joinAsCreator(theCreator: PlayerRmqProxy) {
    this.room.join(theCreator)
    this.room.creator = theCreator
    this.room.creatorName = theCreator.model.name;
  }
}
